using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Nivot.PowerShell.WindowsMobile.Rapi2
{
    /// <summary>
    /// Represents a remote device. This can only be accessed through <see cref="RemoteDeviceManager.Devices"/>.
    /// </summary>
    public class RemoteDevice
    {
        private const int MAX_PATH = 260;
        private IRAPIDevice iDev;
        private IRAPISession iSession;

        internal RemoteDevice(IRAPIDevice iDev)
        {
            this.iDev = iDev;
        }

        private IRAPISession _session
        {
            get
            {
                if (iSession == null)
                {
                    iSession = iDev.CreateSession();
                    iSession.CeRapiInit();
                }
                return iSession;
            }
        }

        /// <summary>
        /// Gets the connection status of the device.
        /// </summary>
        public DeviceStatus Status
        {
            get { return iDev.GetConnectStat(); }
        }

        private RAPI_CONNECTIONINFO ConnInfo
        {
            get
            {
                RAPI_CONNECTIONINFO ci = new RAPI_CONNECTIONINFO();
                iDev.GetConnectionInfo(ref ci);
                return ci;
            }
        }

        /// <summary>
        /// Gets the means by which the device is connected.
        /// </summary>
        public ConnectionType ConnectionType
        {
            get { return ConnInfo.connectionType; }
        }

        /// <summary>
        /// Gets the host address of the connected desktop.
        /// </summary>
        public System.Net.IPAddress HostAddress
        {
            get { return new System.Net.IPAddress(ConnInfo.hostIpaddr.addr); }
        }

        /// <summary>
        /// Gets the assigned address for the device.
        /// </summary>
        public System.Net.IPAddress IPAddress
        {
            get { return new System.Net.IPAddress(ConnInfo.ipaddr.addr); }
        }

        private RAPI_DEVICEINFO DevInfo
        {
            get
            {
                RAPI_DEVICEINFO di = new RAPI_DEVICEINFO();
                iDev.GetDeviceInfo(ref di);
                return di;
            }
        }

        /// <summary>
        /// Gets the name of the device.
        /// </summary>
        public string Name
        {
            get { return DevInfo.bstrName; }
        }

        /// <summary>
        /// Gets a string representation of the device platform.
        /// </summary>
        public string Platform
        {
            get { return DevInfo.bstrPlatform; }
        }

        /// <summary>
        /// Gets the unique identifier for the device.
        /// </summary>
        public Guid DeviceId
        {
            get { return DevInfo.DeviceId; }
        }

        /// <summary>
        /// Gets the <see cref="SystemInformation"/> for the device.
        /// </summary>
        public SystemInformation SystemInformation
        {
            get
            {
                SystemInformation si = new SystemInformation();
                _session.CeGetSystemInfo(ref si);
                return si;
            }
        }

        /// <summary>
        /// Gets the last error raised by the device.
        /// </summary>
        public int LastError
        {
            get { return _session.CeGetLastError(); }
        }

        /// <summary>
        /// Gets the version of the device OS.
        /// </summary>
        public Version OSVersion
        {
            get
            {
                CEOSVERSIONINFO ver = new CEOSVERSIONINFO();
                _session.CeGetVersionEx(ref ver);
                return new Version(ver.dwMajorVersion, ver.dwMinorVersion,
                                   ver.dwBuildNumber, 0);
            }
        }

        /// <summary>
        /// Gets the <see cref="MemoryStatus"/> for the device.
        /// </summary>
        public MemoryStatus MemoryStatus
        {
            get
            {
                MemoryStatus stat = new MemoryStatus();
                _session.CeGlobalMemoryStatus(ref stat);
                return stat;
            }
        }

        /// <summary>
        /// Gets the <see cref="PowerStatus"/> for the device.
        /// </summary>
        public PowerStatus PowerStatus
        {
            get
            {
                PowerStatus stat = new PowerStatus();
                _session.CeGetSystemPowerStatusEx(ref stat, 1);
                return stat;
            }
        }

        /// <summary>
        /// Gets the <see cref="StoreInfo"/> with information about the object store on the device.
        /// </summary>
        public StoreInfo StoreInfo
        {
            get
            {
                StoreInfo stat = new StoreInfo();
                _session.CeGetStoreInformation(ref stat);
                return stat;
            }
        }

        #region General methods

        /// <summary>
        /// This method compares a specified string to the system password on a remote device.
        /// </summary>
        /// <param name="pwd">Password to compare with the system password.</param>
        /// <returns>true if password matches. Otherwise false.</returns>
        public bool CheckPassword(string pwd)
        {
            return Convert.ToBoolean(_session.CeCheckPassword(pwd));
        }

        /// <summary>
        /// Starts the synchronization process with the device.
        /// </summary>
        public void StartSync()
        {
            _session.CeSyncStart(null);
        }

        /// <summary>
        /// Stops the synchronization process with the device.
        /// </summary>
        public void StopSync()
        {
            _session.CeSyncStop();
        }

        /// <summary>
        /// Runs a program on a remote device. It creates a new process and its primary thread. The new process executes the specified executable file.
        /// </summary>
        /// <param name="applicationName">String that specifies the module to execute. <para>The string can specify the full path and file name of the module to execute or it can specify just the module name. In the case of a partial name, the function uses the current drive and current directory to complete the specification.</para></param>
        /// <param name="commandLine">String that specifies the command line arguments with which the application will be executed. 
        /// <para>The commandLine parameter can be NULL. In that case, the method uses the string pointed to by applicationName as the command line.</para>
        /// <para>If commandLine is non-NULL, applicationName specifies the module to execute, and commandLine specifies the command line arguments.</para></param>
        /// <param name="creationFlags">Optional conditions for creating the process.</param>
        public void CreateProcess(string applicationName, string commandLine, ProcessCreationFlags creationFlags)
        {
            PROCESS_INFORMATION pi;
            if (0 ==
                _session.CeCreateProcess(applicationName, commandLine, 0, 0, 0, (int) creationFlags, 0, 0, 0, out pi))
            {
                ThrowRAPIException();
            }
        }

        /// <summary>
        /// Retrieves device-specific information about a remote device.
        /// </summary>
        /// <param name="index">Item to retrieve information on.</param>
        /// <returns>The return value of the specified item.</returns>
        public int GetDeviceCaps(DeviceCapsItem index)
        {
            return _session.CeGetDesktopDeviceCaps((int) index);
        }

        /// <summary>
        /// This method retrieves the dimensions of display elements and system configuration settings of a remote device. All dimensions are in pixels.
        /// </summary>
        /// <param name="index">Item to retrieve information on.</param>
        /// <returns>The return value of the specified item.</returns>
        public int GetSystemMetrics(SystemMetricsItem index)
        {
            return _session.CeGetSystemMetrics((int) index);
        }

        /// <summary>
        /// Retrieves the path to a special shell folder on a remote device.
        /// </summary>
        /// <param name="folder">SpecialFolder enumeration.</param>
        /// <returns>Path of special folder on device.</returns>
        /// <exception cref="RAPIException">Thrown on error retrieving the folder.</exception>
        public string GetFolderPath(SpecialFolder folder)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder(MAX_PATH);
            if (0 == _session.CeGetSpecialFolderPath((int) folder, MAX_PATH, sb))
            {
                ThrowRAPIException();
            }
            return sb.ToString();
        }

        /// <summary>
        /// Gets the path to the directory designated for temporary files on a remote device.
        /// </summary>
        /// <returns>Temporary path.</returns>
        public string GetTempPath()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder(MAX_PATH);
            if (0 == _session.CeGetTempPath(MAX_PATH, sb))
            {
                ThrowRAPIException();
            }
            return sb.ToString();
        }

        public void CreateShortcut(string shortcutFileName, string targetFileName)
        {
            if (0 == _session.CeSHCreateShortcut(shortcutFileName, targetFileName))
            {
                ThrowRAPIException();
            }
        }

        public string GetShortcutTarget(string shortcutFileName)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder(MAX_PATH);
            if (0 == _session.CeSHGetShortcutTarget(shortcutFileName, sb, MAX_PATH))
            {
                ThrowRAPIException();
            }
            return sb.ToString();
        }

        private void ThrowRAPIException()
        {
            throw new RapiException(_session.CeGetLastError());
        }

        #endregion

        #region File methods

        private const uint CREATE_ALWAYS = 2;
        private const uint CREATE_NEW = 1;
        private const int ERROR_NO_MORE_FILES = 18;
        private const uint FILE_ATTRIBUTE_NORMAL = 0x80;
        private const int FILE_SHARE_READ = 0x00000001;
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const int INVALID_HANDLE_VALUE = -1;
        private const uint OPEN_EXISTING = 3;

        /// <summary>
        /// Copies a file from the local system to a remote device.
        /// </summary>
        /// <param name="desktopFileName">The name of the local file to copy.</param>
        /// <param name="deviceFileName">The name of the remote destination file.</param>
        /// <param name="overwrite">true if the destination file can be overwritten; otherwise, false.</param>
        public void CopyFileToDevice(string desktopFileName, string deviceFileName, bool overwrite)
        {
            using (
                DeviceFile remoteFile = new DeviceFile(_session, deviceFileName, GENERIC_WRITE, 0,
                                                       overwrite ? CREATE_ALWAYS : CREATE_NEW, FILE_ATTRIBUTE_NORMAL))
            {
                using (FileStream localFile = new FileStream(desktopFileName, FileMode.Open, FileAccess.Read)) // FIX: default is read/write - fails with UAC
                    remoteFile.WriteStream(localFile);

                remoteFile.SetFileTimes(File.GetCreationTime(desktopFileName), File.GetLastAccessTime(desktopFileName),
                                        File.GetLastWriteTime(desktopFileName));
            }
        }

        /// <summary>
        /// Copies a file from the remote device to the local system.
        /// </summary>
        /// <param name="deviceFileName">The name of the remote file to copy.</param>
        /// <param name="desktopFileName">The name of the local destination file.</param>
        /// <param name="overwrite">true if the destination file can be overwritten; otherwise, false.</param>
        public void CopyFileFromDevice(string deviceFileName, string desktopFileName, bool overwrite)
        {
            using (DeviceFile remoteFile = new DeviceFile(_session, deviceFileName))
            {
                // create the local file
                FileStream localFile = new FileStream(desktopFileName, overwrite ? FileMode.Create : FileMode.CreateNew,
                                                      FileAccess.Write);

                try
                {
                    // read data from remote file into buffer
                    byte[] buffer = new byte[0x1000]; // 4k transfer buffer
                    int bytesread = 0;
                    if (0 == _session.CeReadFile(remoteFile, buffer, 0x1000, ref bytesread, IntPtr.Zero))
                    {
                        ThrowRAPIException();
                    }
                    while (bytesread > 0)
                    {
                        // write it into local file
                        localFile.Write(buffer, 0, bytesread);

                        // get more data
                        bytesread = 0;
                        if (0 == _session.CeReadFile(remoteFile, buffer, 0x1000, ref bytesread, IntPtr.Zero))
                        {
                            ThrowRAPIException();
                        }
                    }
                }
                finally
                {
                    // close the local file
                    localFile.Flush();
                    localFile.Close();
                }
            }
        }

        /// <summary>
        /// Copies a file on the device to another location on the device.
        /// </summary>
        /// <param name="sourceFileName">The name of the file to copy.</param>
        /// <param name="destFileName">The name of the destination file.</param>
        /// <param name="overwrite">true if the destination file can be overwritten; otherwise, false.</param>
        public void CopyFile(string sourceFileName, string destFileName, bool overwrite)
        {
            if (0 == _session.CeCopyFile(sourceFileName, destFileName, overwrite ? 0 : 1))
            {
                ThrowRAPIException();
            }
        }

        /// <summary>
        /// Renames an existing file or a directory on a remote device.
        /// </summary>
        /// <param name="sourceFileName">Name of an existing file or directory.</param>
        /// <param name="newFileName">New name of a file or directory. The new name must not already exist. A new file may be on a different file system or drive. A new directory must be on the same drive.</param>
        public void MoveFile(string sourceFileName, string newFileName)
        {
            if (0 == _session.CeMoveFile(sourceFileName, newFileName))
            {
                ThrowRAPIException();
            }
        }

        /// <summary>
        /// Deletes a file from the object store on a remote device.
        /// </summary>
        /// <param name="fileName">Specified the file to be deleted.</param>
        public void DeleteFile(string fileName)
        {
            if (0 == _session.CeDeleteFile(fileName))
            {
                ThrowRAPIException();
            }
        }

        /// <summary>
        /// Creates a new directory on a remote device.
        /// </summary>
        /// <param name="pathName">String that specifies the path of the directory to be created.</param>
        public void CreateDirectory(string pathName)
        {
            if (pathName.Length > (MAX_PATH - 1))
            {
                throw new ArgumentException(
                    string.Format("Directory paths may not exceed {0} characters.", MAX_PATH - 1), "pathName");
            }
            if (0 == _session.CeCreateDirectory(pathName, IntPtr.Zero))
            {
                ThrowRAPIException();
            }
        }

        /// <summary>
        /// Deletes an existing empty directory on a remote device.
        /// </summary>
        /// <param name="pathName">String that specifies the path of the directory to be removed. The path must specify an empty directory, and the calling process must have write access to the directory.</param>
        public void RemoveDirectory(string pathName)
        {
            if (0 == _session.CeRemoveDirectory(pathName))
            {
                ThrowRAPIException();
            }
        }

        /// <summary>
        /// Returns attributes for a specified file or directory on a remote device.
        /// </summary>
        /// <param name="fileName">String that specifies the name of a file or directory.</param>
        /// <returns>Attributes of the specified file or directory.</returns>
        //public System.IO.FileAttributes GetFileAttributes(string fileName)
        public CEFileAttributes GetFileAttributes(string fileName)
        {
            uint ret = _session.CeGetFileAttributes(fileName);
            if (ret == 0xFFFFFFFF)
            {
                ThrowRAPIException();
            }
            return (CEFileAttributes) ret;
        }

        /// <summary>
        /// Sets the attributes of a file or directory on a remote device.
        /// </summary>
        /// <param name="fileName">String that specifies the name of a file or directory.</param>
        /// <param name="attr">Specifies the file attributes to set for the file.</param>
        public void SetFileAttributes(string fileName, CEFileAttributes attr)
        {
            if (0 == _session.CeSetFileAttributes(fileName, (uint) attr))
            {
                ThrowRAPIException();
            }
        }

        /// <summary>
        /// Gets the date and time the specified file was created on a remote device.
        /// </summary>
        /// <param name="fileName">String that specifies the name of a file.</param>
        /// <returns>Creation date and time</returns>
        public DateTime GetFileCreationTime(string fileName)
        {
            using (DeviceFile f = new DeviceFile(_session, fileName))
                return f.GetFileTimes().CreationTime;
        }

        /// <summary>
        /// Gets the date and time the specified file was last accessed on a remote device.
        /// </summary>
        /// <param name="fileName">String that specifies the name of a file.</param>
        /// <returns>Last access date and time</returns>
        public DateTime GetFileLastAccessTime(string fileName)
        {
            using (DeviceFile f = new DeviceFile(_session, fileName))
                return f.GetFileTimes().LastAccessTime;
        }

        /// <summary>
        /// Gets the date and time the specified file was last modified on a remote device.
        /// </summary>
        /// <param name="fileName">String that specifies the name of a file.</param>
        /// <returns>Last modified date and time</returns>
        public DateTime GetFileLastWriteTime(string fileName)
        {
            using (DeviceFile f = new DeviceFile(_session, fileName))
                return f.GetFileTimes().LastWriteTime;
        }

        /// <summary>
        /// Sets the date and time that a file was created, last accessed, or last modified for files on a remote device.
        /// </summary>
        /// <param name="fileName">String that specifies the name of a file.</param>
        /// <param name="creationTime">The date and time the file was created. This parameter can be NULL if the application does not need to set this information.</param>
        /// <param name="lastAccessTime">The date and time the file was last accessed. This parameter can be NULL if the application does not need to set this information.</param>
        /// <param name="lastWriteTime">The date and time the file was last modified. This parameter can be NULL if the application does not need to set this information.</param>
        public void SetFileTimes(string fileName, DateTime? creationTime, DateTime? lastAccessTime,
                                 DateTime? lastWriteTime)
        {
            using (DeviceFile f = new DeviceFile(_session, fileName))
                f.SetFileTimes(creationTime, lastAccessTime, lastWriteTime);
        }

        /// <summary>
        /// Gets the size, in bytes, of a file on a remote device.
        /// </summary>
        /// <param name="fileName">String that specifies the name of a file.</param>
        /// <returns>Size, in bytes, of the file.</returns>
        public ulong GetFileSize(string fileName)
        {
            using (DeviceFile f = new DeviceFile(_session, fileName))
                return f.Size;
        }

        /// <summary>
        /// Reads the contents of a file on a remote device into a array of bytes.
        /// </summary>
        /// <param name="deviceFileName">String that specifies the name of an existing file.</param>
        /// <returns>Contents of file in an array of bytes.</returns>
        public byte[] ReadFileAllBytes(string deviceFileName)
        {
            byte[] ret = new byte[0];
            using (DeviceFile remoteFile = new DeviceFile(_session, deviceFileName))
            {
                using (MemoryStream mem = new MemoryStream(10240))
                {
                    // read data from remote file into buffer
                    byte[] buffer = new byte[0x1000]; // 4k transfer buffer
                    int bytesread = 0;
                    if (0 == _session.CeReadFile(remoteFile, buffer, 0x1000, ref bytesread, IntPtr.Zero))
                    {
                        ThrowRAPIException();
                    }
                    while (bytesread > 0)
                    {
                        // write it into local file
                        mem.Write(buffer, 0, bytesread);

                        // get more data
                        bytesread = 0;
                        if (0 == _session.CeReadFile(remoteFile, buffer, 0x1000, ref bytesread, IntPtr.Zero))
                        {
                            ThrowRAPIException();
                        }
                    }
                    ret = mem.ToArray();
                }
            }
            return ret;
        }

        /// <summary>
        /// Opens a text file on a remote device, reads all lines of the file into a string, and then closes the file.
        /// </summary>
        /// <param name="fileName">String that specifies the name of an existing text file.</param>
        /// <param name="encoding">The encoding applied to the contents of the file.</param>
        /// <returns>A string containing all lines of the file.</returns>
        public string ReadFileAllText(string fileName, Encoding encoding)
        {
            StringBuilder sb = new StringBuilder();
            using (DeviceFile remoteFile = new DeviceFile(_session, fileName))
            {
                // read data from remote file into buffer
                byte[] buffer = new byte[0x1000]; // 4k transfer buffer
                int bytesread = 0;
                if (0 == _session.CeReadFile(remoteFile, buffer, 0x1000, ref bytesread, IntPtr.Zero))
                {
                    ThrowRAPIException();
                }
                while (bytesread > 0)
                {
                    // write it 
                    sb.Append(encoding.GetString(buffer));

                    // get more data
                    bytesread = 0;
                    if (0 == _session.CeReadFile(remoteFile, buffer, 0x1000, ref bytesread, IntPtr.Zero))
                    {
                        ThrowRAPIException();
                    }
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// Returns the names of files in the specified directory that match the specified search pattern.
        /// </summary>
        /// <param name="path">The directory to search.</param>
        /// <param name="searchPattern">The search string to match against the names of files in path.</param>
        /// <returns>A String array containing the names of files in the specified directory that match the specified search pattern.</returns>
        public string[] GetFiles(string path, string searchPattern, CeFindAllFilesFlags flags)
        {
            int foundCount = 0;
            IntPtr findDataArray;
            if (0 ==
                _session.CeFindAllFiles(Path.Combine(path, searchPattern), (int) flags /*0x80*/, ref foundCount,
                                        out findDataArray))
            {
                ThrowRAPIException();
            }

            var list = new System.Collections.Generic.List<string>(foundCount);
            try
            {
                CE_FIND_DATA[] fds = new CE_FIND_DATA[foundCount];
                IntPtr current = findDataArray;
                for (int i = 0; i < foundCount; i++)
                {
                    fds[i] = (CE_FIND_DATA) Marshal.PtrToStructure(current, typeof (CE_FIND_DATA));
                    list.Add(fds[i].Name);
                    current = (IntPtr) ((long) current + Marshal.SizeOf(fds[i]));
                }
            }
            finally
            {
                _session.CeRapiFreeBuffer(findDataArray);
            }
            return list.ToArray();
        }

        public string[] GetFiles(string path, string searchPattern)
        {
            return GetFiles(path, searchPattern, CeFindAllFilesFlags.FAF_NAME);
        }

        /// <summary>
        /// Retrieves the amount of space on a disk volume on a remote device.
        /// </summary>
        /// <param name="drivePath">String that specifies a directory on a disk.</param>
        /// <returns><see cref="DriveInfo"/> structure with information about specified disk.</returns>
        public DriveInfo GetDriveInfo(string drivePath)
        {
            DriveInfo di = new DriveInfo();
            if (0 ==
                _session.CeGetDiskFreeSpaceEx(drivePath, ref di.AvailableFreeSpace, ref di.TotalSize,
                                              ref di.TotalFreeSpace))
            {
                ThrowRAPIException();
            }
            return di;
        }

        #region Nested type: DeviceFile

        private class DeviceFile : SafeHandle
        {
            private IRAPISession sess;

            internal DeviceFile(IRAPISession sess, string fileName, uint dwDesiredAccess, uint dwShareMode,
                                uint dwCreationDistribution, uint dwFlags)
                : base(new IntPtr(INVALID_HANDLE_VALUE), true)
            {
                this.sess = sess;
                base.SetHandle(sess.CeCreateFile(fileName, dwDesiredAccess, dwShareMode, IntPtr.Zero,
                                                 dwCreationDistribution, dwFlags, IntPtr.Zero));
                if (this.IsInvalid)
                {
                    throw new RapiException(sess.CeGetLastError());
                }
            }

            internal DeviceFile(IRAPISession sess, string fileName)
                : this(sess, fileName, GENERIC_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL)
            {
            }

            public ulong Size
            {
                get
                {
                    uint size = 0;
                    uint res = sess.CeGetFileSize(base.handle, ref size);
                    if (res == (uint) 0xFFFFFFFF)
                    {
                        throw new RapiException(sess.CeRapiGetError());
                    }
                    return (ulong) res + ((ulong) size << 32);
                }
            }

            public override bool IsInvalid
            {
                get { return base.handle == IntPtr.Zero || (int) base.handle == INVALID_HANDLE_VALUE; }
            }

            public FileTimes GetFileTimes()
            {
                FileTimes ft = new FileTimes();
                if (0 == sess.CeGetFileTime(base.handle, ref ft.cft, ref ft.aft, ref ft.wft))
                {
                    throw new RapiException(sess.CeGetLastError());
                }
                return ft;
            }

            public void SetFileTimes(DateTime? creationTime, DateTime? lastAccessTime, DateTime? lastWriteTime)
            {
                CFILETIME cft = creationTime.HasValue ? new CFILETIME(creationTime.Value) : null;
                CFILETIME aft = lastAccessTime.HasValue ? new CFILETIME(lastAccessTime.Value) : null;
                CFILETIME wft = lastWriteTime.HasValue ? new CFILETIME(lastWriteTime.Value) : null;
                if (0 == sess.CeSetFileTime(base.handle, cft, aft, wft))
                {
                    throw new RapiException(sess.CeGetLastError());
                }
            }

            public void WriteStream(System.IO.Stream stream)
            {
                // read 4k of data
                int byteswritten = 0;
                int filepos = 0;
                byte[] buffer = new byte[0x1000]; // 4k transfer buffer
                int bytesread = stream.Read(buffer, filepos, buffer.Length);
                while (bytesread > 0)
                {
                    // move remote file pointer # of bytes read
                    filepos += bytesread;

                    // write our buffer to the remote file
                    if (0 == sess.CeWriteFile(base.handle, buffer, bytesread, ref byteswritten, IntPtr.Zero))
                    {
                        throw new RapiException(sess.CeGetLastError());
                    }
                    try
                    {
                        // refill the local buffer
                        bytesread = stream.Read(buffer, 0, buffer.Length);
                    }
                    catch (Exception)
                    {
                        bytesread = 0;
                    }
                }
            }

            protected override bool ReleaseHandle()
            {
                if (!IsInvalid)
                {
                    if (0 == sess.CeCloseHandle(base.handle))
                    {
                        return false;
                    }
                }
                return true;
            }

            /// <summary>
            /// Allows to use DeviceFile as IntPtr
            /// </summary>
            public static implicit operator IntPtr(DeviceFile f)
            {
                return f.DangerousGetHandle();
            }

            #region Nested type: FileTimes

            public struct FileTimes
            {
                public System.Runtime.InteropServices.ComTypes.FILETIME aft;
                public System.Runtime.InteropServices.ComTypes.FILETIME cft;
                public System.Runtime.InteropServices.ComTypes.FILETIME wft;

                public DateTime CreationTime
                {
                    get { return cft.ToDateTime(); }
                    set { cft = value.ToFILETIME(); }
                }

                public DateTime LastAccessTime
                {
                    get { return aft.ToDateTime(); }
                    set { aft = value.ToFILETIME(); }
                }

                public DateTime LastWriteTime
                {
                    get { return wft.ToDateTime(); }
                    set { wft = value.ToFILETIME(); }
                }
            }

            #endregion
        }

        #endregion

        #region Nested type: DriveInfo

        /// <summary>Information about a space on a disk.</summary>
        public struct DriveInfo
        {
            /// <summary>The total number of free bytes on a disk that are available to the user.</summary>
            public ulong AvailableFreeSpace;

            /// <summary>The total number of bytes on a disk that are available to the user.</summary>
            public ulong TotalFreeSpace;

            /// <summary>The total number of free bytes on a disk</summary>
            public ulong TotalSize;
        }

        #endregion

        #endregion

        #region Registry methods

        /// <summary>Reads the remote device's registry base key HKEY_CLASSES_ROOT.</summary>
        public DeviceRegistryKey DeviceRegistryClassesRoot
        {
            get { return new DeviceRegistryKey(_session, 0x80000000, "HKEY_CLASSES_ROOT"); }
        }

        /// <summary>Reads the remote device's registry base key HKEY_CURRENT_USER.</summary>
        public DeviceRegistryKey DeviceRegistryCurrentUser
        {
            get { return new DeviceRegistryKey(_session, 0x80000001, "HKEY_CURRENT_USER"); }
        }

        /// <summary>Reads the remote device's registry base key HKEY_LOCAL_MACHINE.</summary>
        public DeviceRegistryKey DeviceRegistryLocalMachine
        {
            get { return new DeviceRegistryKey(_session, 0x80000002, "HKEY_LOCAL_MACHINE"); }
        }

        /// <summary>Reads the remote device's registry base key HKEY_USERS.</summary>
        public DeviceRegistryKey DeviceRegistryUsers
        {
            get { return new DeviceRegistryKey(_session, 0x80000003, "HKEY_USERS"); }
        }

        /// <summary>
        /// Represents a key-level node in the remote device's registry. This class is a registry encapsulation.
        /// </summary>
        public sealed class DeviceRegistryKey : IDisposable
        {
            private const int ERROR_MORE_DATA = 234;
            private const int ERROR_NO_MORE_ITEMS = 259;
            private const int ERROR_SUCCESS = 0;
            private uint hKey = 0;
            private IRAPISession sess;

            internal DeviceRegistryKey(IRAPISession session)
            {
                sess = session;
            }

            internal DeviceRegistryKey(IRAPISession session, uint handle, string keyName)
            {
                sess = session;
                hKey = handle;
                Name = keyName;
            }

            public string Name { get; private set; }

            #region IDisposable Members

            /// <summary>
            /// Performs a close on the current key.
            /// </summary>
            public void Dispose()
            {
                sess.CeRegCloseKey(hKey);
                hKey = 0;
                GC.SuppressFinalize(this);
            }

            #endregion

            /// <summary>
            /// Closes the key and flushes it to disk if its contents have been modified.
            /// </summary>
            public void Close()
            {
                this.Dispose();
            }

            /// <summary>
            /// Creates a new subkey or opens an existing subkey.
            /// </summary>
            /// <param name="subkey">Name of key to create.</param>
            /// <returns>A <see cref="DeviceRegistryKey"/> object that represents the newly created subkey, or <c>null</c> if the operation failed.</returns>
            public DeviceRegistryKey CreateSubKey(string subkey)
            {
                EnsureNotDisposed();
                uint hNewKey = 0, disp = 0;
                int ret = sess.CeRegCreateKeyEx(hKey, subkey, 0, string.Empty, 0, 0, IntPtr.Zero, ref hNewKey, ref disp);
                if (ret != ERROR_SUCCESS)
                {
                    throw new RapiException(ret);
                }
                return new DeviceRegistryKey(sess, hNewKey, this.Name + "\\" + subkey);
            }

            /// <summary>
            /// Deletes the specified subkey. 
            /// </summary>
            /// <param name="subkey">The name of the subkey to delete.</param>
            public void DeleteSubKey(string subkey)
            {
                EnsureNotDisposed();
                int ret = sess.CeRegDeleteKey(hKey, subkey);
                if (ret != ERROR_SUCCESS)
                {
                    throw new RapiException(ret);
                }
            }

            /// <summary>
            /// Deletes the specified value from this key.
            /// </summary>
            /// <param name="name">The name of the value to delete.</param>
            public void DeleteValue(string name)
            {
                EnsureNotDisposed();
                int ret = sess.CeRegDeleteValue(hKey, name);
                if (ret != ERROR_SUCCESS)
                {
                    throw new RapiException(ret);
                }
            }

            private void EnsureNotDisposed()
            {
                if (this.hKey == 0)
                {
                    throw new ObjectDisposedException("DeviceRegistryKey");
                }
            }

            /// <summary>
            /// Retrieves an array of strings that contains all the subkey names.
            /// </summary>
            /// <returns>An array of strings that contains the names of the subkeys for the current key.</returns>
            public string[] GetSubKeyNames()
            {
                uint idx = 0;
                StringBuilder sb = new StringBuilder(MAX_PATH);
                StringBuilder sbNull = new StringBuilder(MAX_PATH);
                uint cbName = MAX_PATH;
                uint cbClass = MAX_PATH;
                int ret = 0;
                System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();
                do
                {
                    cbName = (uint) sb.Capacity;
                    ret = sess.CeRegEnumKeyEx(hKey, idx, sb, ref cbName, 0, sbNull, ref cbClass, IntPtr.Zero);
                    if (ret == ERROR_MORE_DATA)
                    {
                        sb.Capacity = (int) cbName + 1;
                        ret = sess.CeRegEnumKeyEx(hKey, idx, sb, ref cbName, 0, sbNull, ref cbClass, IntPtr.Zero);
                    }
                    if (ret == ERROR_SUCCESS)
                    {
                        list.Add(sb.ToString());
                    }
                    if (ret == ERROR_NO_MORE_ITEMS)
                    {
                        break;
                    }
                    idx++;
                } while (ret == ERROR_SUCCESS);

                if (ret != ERROR_NO_MORE_ITEMS)
                {
                    throw new RapiException(ret);
                }

                return list.ToArray();
            }

            /// <summary>
            /// Retrieves the value associated with the specified name. If the name is not found, returns the default value that you provide.
            /// </summary>
            /// <param name="name">The name of the value to retrieve.</param>
            /// <param name="defaultValue">The value to return if name does not exist.</param>
            /// <returns>The value associated with name, with any embedded environment variables left unexpanded, or defaultValue if name is not found.</returns>
            public object GetValue(string name, object defaultValue)
            {
                EnsureNotDisposed();
                int lpType = 0;
                int cbData = 0;
                int ret = sess.CeRegQueryValueEx(hKey, name, IntPtr.Zero, out lpType, IntPtr.Zero, ref cbData);
                if (ret != ERROR_SUCCESS)
                {
                    throw new RapiException(ret);
                }
                HGlobalSafeHandle data = new HGlobalSafeHandle(cbData);
                ret = sess.CeRegQueryValueEx(hKey, name, IntPtr.Zero, out lpType, data, ref cbData);
                if (ret != 0)
                {
                    throw new RapiException(ret);
                }
                if (data == IntPtr.Zero)
                {
                    return defaultValue;
                }
                switch (lpType)
                {
                    case (int) Microsoft.Win32.RegistryValueKind.DWord:
                        return data.DangerousGetHandle().ToInt32();
                    case (int) Microsoft.Win32.RegistryValueKind.ExpandString:
                    case (int) Microsoft.Win32.RegistryValueKind.String:
                        return data.ToString();
                    case (int) Microsoft.Win32.RegistryValueKind.Binary:
                    case (int) Microsoft.Win32.RegistryValueKind.MultiString:
                        byte[] buffer = new byte[cbData];
                        Marshal.Copy(data, buffer, 0, cbData);
                        if (lpType == (int) Microsoft.Win32.RegistryValueKind.MultiString)
                        {
                            System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();
                            int head = 0;
                            for (int i = 1; i < cbData; i++)
                            {
                                if (buffer[i] == 0)
                                {
                                    if (i == head)
                                    {
                                        list.Add(string.Empty);
                                    }
                                    else
                                    {
                                        list.Add(Encoding.Default.GetString(buffer, head, i - head));
                                    }
                                    i++;
                                    head = i;
                                    if (buffer[head] == 0)
                                    {
                                        break;
                                    }
                                }
                            }
                            return list.ToArray();
                        }
                        return buffer;
                    default:
                        return defaultValue;
                }
            }

            /// <summary>
            /// Gets the type of value in a registry value.
            /// </summary>
            /// <param name="name">The name of the value whose registry data type is to be retrieved.</param>
            /// <returns>A RegistryValueKind value representing the registry data type of the value associated with name.</returns>
            public Microsoft.Win32.RegistryValueKind GetValueKind(string name)
            {
                EnsureNotDisposed();
                int lpType = 0;
                int lpcbData = 0;
                int ret = sess.CeRegQueryValueEx(hKey, name, IntPtr.Zero, out lpType, IntPtr.Zero, ref lpcbData);
                if (ret != ERROR_SUCCESS)
                {
                    throw new RapiException(ret);
                }
                if (!Enum.IsDefined(typeof (Microsoft.Win32.RegistryValueKind), lpType))
                {
                    return Microsoft.Win32.RegistryValueKind.Unknown;
                }
                return (Microsoft.Win32.RegistryValueKind) lpType;
            }

            /// <summary>
            /// Retrieves an array of strings that contains all the value names associated with this key.
            /// </summary>
            /// <returns>An array of strings that contains the value names for the current key.</returns>
            public string[] GetValueNames()
            {
                StringBuilder sb = new StringBuilder(MAX_PATH);
                uint idx = 0, cbName = MAX_PATH, cbData = 0, lpType;
                int ret = 0;
                System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();
                do
                {
                    cbName = (uint) sb.Capacity;
                    ret = sess.CeRegEnumValue(hKey, idx, sb, ref cbName, 0, out lpType, IntPtr.Zero, ref cbData);
                    if (ret == ERROR_MORE_DATA)
                    {
                        sb.Capacity = (int) cbName + 1;
                        ret = sess.CeRegEnumValue(hKey, idx, sb, ref cbName, 0, out lpType, IntPtr.Zero, ref cbData);
                    }
                    if (ret == ERROR_SUCCESS)
                    {
                        list.Add(sb.ToString());
                    }
                    if (ret == ERROR_NO_MORE_ITEMS)
                    {
                        break;
                    }
                    idx++;
                } while (ret == ERROR_SUCCESS);

                if (ret != ERROR_NO_MORE_ITEMS)
                {
                    throw new RapiException(ret);
                }

                return list.ToArray();
            }

            /// <summary>
            /// Retrieves a subkey as read-only.
            /// </summary>
            /// <param name="name">The name or path of the subkey to open read-only.</param>
            /// <returns>The subkey requested, or null if the operation failed.</returns>
            public DeviceRegistryKey OpenSubKey(string name)
            {
                EnsureNotDisposed();
                uint hNewKey = 0;
                int ret = sess.CeRegOpenKeyEx(hKey, name, 0, 0, ref hNewKey);
                if (ret != ERROR_SUCCESS)
                {
                    throw new RapiException(ret);
                }
                return new DeviceRegistryKey(sess, hNewKey, this.Name + "\\" + name);
            }

            /// <summary>
            /// Sets the specified name/value pair.
            /// </summary>
            /// <param name="name">The name of the value to store.</param>
            /// <param name="value">The data to be stored.</param>
            public void SetValue(string name, object value)
            {
                Microsoft.Win32.RegistryValueKind valueKind = Microsoft.Win32.RegistryValueKind.Unknown;
                if (value is int)
                {
                    valueKind = Microsoft.Win32.RegistryValueKind.DWord;
                }
                else if (value is string)
                {
                    valueKind = Microsoft.Win32.RegistryValueKind.String;
                }
                else if (value is byte[])
                {
                    valueKind = Microsoft.Win32.RegistryValueKind.Binary;
                }
                else if (value is string[])
                {
                    valueKind = Microsoft.Win32.RegistryValueKind.MultiString;
                }
                if (valueKind == Microsoft.Win32.RegistryValueKind.Unknown)
                {
                    throw new ArgumentException("value must be of a known type");
                }
                SetValue(name, value, valueKind);
            }

            /// <summary>
            /// Sets the value of a name/value pair in the registry key, using the specified registry data type.
            /// </summary>
            /// <param name="name">The name of the value to store.</param>
            /// <param name="value">The data to be stored.</param>
            /// <param name="valueKind">The registry data type to use when storing the data.</param>
            public void SetValue(string name, object value, Microsoft.Win32.RegistryValueKind valueKind)
            {
                EnsureNotDisposed();
                HGlobalSafeHandle data = null;
                int cbData = 0;
                switch (valueKind)
                {
                    case Microsoft.Win32.RegistryValueKind.Binary:
                        cbData = ((byte[]) value).Length;
                        data = new HGlobalSafeHandle((byte[]) value);
                        break;
                    case Microsoft.Win32.RegistryValueKind.DWord:
                        cbData = sizeof (int);
                        data = new HGlobalSafeHandle(new IntPtr(Convert.ToInt32(value)));
                        break;
                    case Microsoft.Win32.RegistryValueKind.String:
                    case Microsoft.Win32.RegistryValueKind.ExpandString:
                        string str = value.ToString();
                        byte[] bytes = Encoding.Default.GetBytes(str);
                        cbData = bytes.Length + 1;
                        byte[] destinationArray = new byte[cbData];
                        Array.Copy(bytes, 0, destinationArray, 0, bytes.Length);
                        data = new HGlobalSafeHandle(destinationArray);
                        break;
                    case Microsoft.Win32.RegistryValueKind.QWord:
                        cbData = sizeof (long);
                        data = new HGlobalSafeHandle(BitConverter.GetBytes(Convert.ToInt64(value)));
                        break;
                    case Microsoft.Win32.RegistryValueKind.MultiString:
                        if (value is string[] && ((string[]) value).Length > 0)
                        {
                            for (int i = 0; i < ((string[]) value).Length; i++)
                            {
                                if (((string[]) value)[i] == null)
                                {
                                    throw new ArgumentException("System.String[] value cannot contain null items.");
                                }
                                cbData += Encoding.Default.GetByteCount(((string[]) value)[i]) + 1;
                            }
                            cbData++;
                            byte[] buffer = new byte[cbData];
                            int ptr = 0;
                            for (int i = 0; i < ((string[]) value).Length; i++)
                            {
                                string str2 = ((string[]) value)[i];
                                Encoding.Default.GetBytes(str2, 0, str2.Length, buffer, ptr);
                                ptr += Encoding.Default.GetByteCount(str2);
                                buffer[ptr] = 0;
                                ptr++;
                            }
                            buffer[ptr] = 0;
                            data = new HGlobalSafeHandle(buffer);
                        }
                        else
                        {
                            throw new ArgumentException("Value must be of a System.String[] type.");
                        }
                        break;
                    case Microsoft.Win32.RegistryValueKind.Unknown:
                    default:
                        throw new InvalidOperationException();
                }
                int ret = sess.CeRegSetValueEx(hKey, name, 0, (int) valueKind, data, cbData);
                if (ret != ERROR_SUCCESS)
                {
                    throw new RapiException(ret);
                }
            }

            /// <summary>
            /// Retrieves a string representation of this key.
            /// </summary>
            /// <returns>Key name.</returns>
            public override string ToString()
            {
                return this.Name;
            }
        }

        #endregion
    }
}